/**
  ETFRoot project, Anyang Normal University && IMP-CAS
  \class ETFPXIUnpacker
  \brief unpacker for raw data collected by PXI daq
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2021-09-27
  \date 2021-10-01 last modified
  \attention
  changelog
  <table>
  <tr>  <th>Date        <th>Author      <th>Description                    </tr>
  <tr>  <td>2021-09-27   <td>Asia Sun    <td>file created                  </tr>
  <tr>  <td>2021-10-01   <td>Asia Sun    <td>completing                    </tr>
  </table>

  \copyright Copyright (c) 2021 Anyang Normal U. && IMP-CAS with LGPLv3 LICENSE
*/

#include <iostream>
#include <TClonesArray.h>
#include "FairRootManager.h"

#include "ETFPXIUnpacker.h"
#include "ETFPXISection.h"
#include "ETFPXIFragment.h"
#include "ETFHPTDCChannel.h"
#include "ETFMsg.h"

using std::cout;
using std::endl;
using std::flush;

ClassImp(ETFPXIUnpacker);

ETFPXIUnpacker::ETFPXIUnpacker() : ETFUnpacker(0), fFrag_h(nullptr), fData(nullptr)
  ,fPXISection(new ETFPXISection()),fBunchIDMisAlignCnt(0), fIsCheckBunchID(true){}

ETFPXIUnpacker::~ETFPXIUnpacker(){
  if(fData) delete fData;
  if(fFrag_h) delete fFrag_h;
  if(fPXISection) delete fPXISection;
} // end dtor

bool ETFPXIUnpacker::Init(){
  Register();
  return true;
}

void ETFPXIUnpacker::Register(){
  FairRootManager *man = FairRootManager::Instance();
  if(man){
    // branchname, foldername, array address, persistent or transient
    man->Register("PXISection", "PXI", fPXISection, true);
    man->Register("fragment", "PXI", fPXISection->fFragments, true);
    man->Register("tchpxi", "PXI", fPXISection->fHPTDCChannels, true);
    man->Register("achpxi", "PXI", fPXISection->fAmplitudeChannels, true);
  } // end if
} // end member function Register

void ETFPXIUnpacker::Reset(){
  if(fFrag_h){ delete fFrag_h; fFrag_h = nullptr; }
  if(fData){ delete fData; fData = nullptr; }
  fPXISection->Reset(); // clear the fragments
} // end member function Reset



//----------------------------PXI---------------------------------------------//
//---------------------------UNPACK-------------------------------------------//
//----------------------------CODE--------------------------------------------//

// get one event from offline data file to memory (fData)
// \retval: return index of the event being read
int ETFPXIUnpacker::GetOneEvent(FILE *fp){
  section_head &sec_h = fPXISection->fSectionHead;
  // read the section header, retrieve the number of valid fragments
  if(fread(&sec_h, sizeof(section_head), 1, fp) <= 0) return -1;
  fCurrentEventIndex = sec_h.index;
  fPXISection->fSection_len += sizeof(section_head);
  const int frag_nu = sec_h.fragment_num;
  if(frag_nu <= 0)
    ETFMsg::Info("ETFPXIOfflineSource", "GetOneEvent: This event has \
no data, event index: %d, fragment_num: %d", sec_h.index, frag_nu);
  else{ // read all the fragment head
    fFrag_h = new fragment_head[frag_nu];
    if(fread(fFrag_h, sizeof(fragment_head), frag_nu, fp) <= 0) return -1;
    fPXISection->fSection_len += frag_nu * sizeof(fragment_head);
    // check if empty again, which only has a section head and a fragment head
    if(0 == fFrag_h[0].data_len)
      ETFMsg::Info("ETFPXIOfflineSource", "GetOneEvent: This event has \
  fragment, yet still no data, event index: %d, fragment_num: %d", sec_h.index, frag_nu);
  } // end else
  //----------------- read the data zone of the fragments -----------------//
  if(frag_nu >= FRAG_NUM_LIMIT_PER_EVENT)
    ETFMsg::Info("ETFPXIOfflineSource", "GetOneEvent: fragment number out of limit");
  int total_data_len = 0; // length of the data zone of the event
  for(int i = 0; i < frag_nu; i++) total_data_len += fFrag_h[i].data_len;
  fPXISection->fSection_len += total_data_len;
  total_data_len /= sizeof(int);
  fData = new int[total_data_len];
  if(fread(fData, sizeof(int), total_data_len, fp) <= 0) return -1;

  return fCurrentEventIndex; // reading of one event is now complete
} // end member function GetOneEvent(FILE *fp)

/// get one event from online data stream to memory
/// \TODO on the premise that `stream` updates to point to each new section
/// upon calling this method, or one should explicitly update it here
int ETFPXIUnpacker::GetOneEvent(int *stream){
  int pos = 0; // pointer offset to the current position in the stream
  section_head &sec_h = fPXISection->fSectionHead;
  // read the section header, retrieve the number of valid fragments
  memcpy(&sec_h, stream+pos, sizeof(section_head));
  pos += sizeof(section_head) / sizeof(int);
  fCurrentEventIndex = sec_h.index;
  fPXISection->fSection_len += sizeof(section_head);
  const int frag_nu = sec_h.fragment_num;
  if(frag_nu <= 0)
    ETFMsg::Warn("ETFPXIOfflineSource", "GetOneEvent(_online): This event has \
no data, event index: %d, fragment_num: %d", sec_h.index, frag_nu);
  else{ // read all the fragment head
    fFrag_h = reinterpret_cast<fragment_head *>(stream+pos);
    pos += sizeof(fragment_head)*frag_nu / sizeof(int);
    fPXISection->fSection_len += frag_nu * sizeof(fragment_head);
    // check if empty again, which only has a section head and a fragment head
    if(0 == fFrag_h[0].data_len)
      ETFMsg::Warn("ETFPXIOfflineSource", "GetOneEvent(_online): This event has \
fragment, yet still no data, event index: %d, fragment_num: %d", sec_h.index, frag_nu);
  } // end else
  //----------------- read the data zone of the fragments -----------------//
  if(frag_nu >= FRAG_NUM_LIMIT_PER_EVENT)
    ETFMsg::Warn("ETFPXIOfflineSource", "GetOneEvent(_online): fragment number out of limit");
  int total_data_len = 0; // length of the data zone of the event
  for(int i = 0; i < frag_nu; i++) total_data_len += fFrag_h[i].data_len;
  fData = stream+pos; pos += total_data_len / sizeof(int);
  fPXISection->fSection_len += total_data_len;

  return fCurrentEventIndex;
} // end member function GetOneEvent(int *stream)

/// do the rest of the unpacking after GetOneEvent and store the results in
/// member variable fPXISection
/// \note that the method is called by ETFOfflineSource::ReadEvent, where Unpack()
/// would be bypassed for failure of GetOneEvent()
static const int EDGE_NUM_LIMIT = 10; // maximum edge number in a channel
bool ETFPXIUnpacker::Unpack(){
  if(!fData){
    ETFMsg::Error("ETFPXIUnpacker", "Unpack: fData is nullptr");
    return false;
  }

  int pos = 0; // pointer offset to the current position in the stream
  int va_ch_nu; // valid channel number (e.g.: 16, 128)
  section_head &sec_h = fPXISection->fSectionHead;
  const int frag_nu = sec_h.fragment_num;
  TClonesArray *frags = fPXISection->GetFragments();
  TClonesArray *tchs = fPXISection->GetHPTDCChannels();

  // here we start from the data zone part, i.e. the two tables and the channel data
  for(int i = 0; i < frag_nu; i++){ // loop over fragments
    ETFPXIFragment *frag = dynamic_cast<ETFPXIFragment *>(frags->ConstructedAt(i));
    frag->Reset();
    frag->fFragmentHead = fFrag_h[i];
    const int ch_nu = fFrag_h[i].channel_num;
    //----------------------- READ DATA_VALIDITY_TABLE -------------------------//
    int *ch_id = nullptr; // channel id array
    int data_len = fFrag_h[i].data_len; // data length (including possible tables)
    if(fFrag_h[i].channel_full){ // all the channels in the fragment are fired
      va_ch_nu = ch_nu;
      ch_id = new int[va_ch_nu];
      for(int j = 0; j < va_ch_nu; j++) ch_id[j] = fFrag_h[i].start_channel + j;
    } // end if(channel_full)
    else{ // not all the channels in the fragment are fired, so there is a data_validity_table
      va_ch_nu = *(fData+pos); pos++;
      // read channel id of every fired channels
      ch_id = fData+pos; pos += va_ch_nu;
      data_len -= (va_ch_nu+1)*sizeof(int); // subtract the length of the data_validity_table
    } // end else
    //----------------------- READ CHANNEL_OFFSET_TABLE ----------------------//
    int *edge_num = new int[va_ch_nu - 1]; // number of edges for each channel
    int length_last_ch = -1; // length of the last channel of a fragment, see below
    if(fFrag_h[i].length_change){ // a channel_offset_table exists
      int *offset = fData+pos; pos += va_ch_nu;
      // obtain edge number of each fired channel
      for(int j = 0; j < va_ch_nu - 1; j++) edge_num[j] = (offset[j + 1] - offset[j]) / 4;
      data_len -= va_ch_nu*sizeof(int); // subtract the length of the channel_offset_table
      length_last_ch = (data_len - offset[va_ch_nu - 1]) / sizeof(int); // length in words
    } // end if(length_change)
    else{ // the edge numbers of each channel are the same
      length_last_ch = data_len / (va_ch_nu*sizeof(int));
      for(int j = 0; j < va_ch_nu - 1; j++) edge_num[j] = length_last_ch;
    } // end else
    // ----------------------- PROCESS EACH CHANNEL ------------------------- //
    // loop over fired channels except the last one, which is explained later
    for(int j = 0; j < va_ch_nu - 1; j++) if(edge_num[j] > 0){
      ETFHPTDCChannel *ch =
        dynamic_cast<ETFHPTDCChannel *>(tchs->ConstructedAt(tchs->GetEntries()));
      ch->Reset();
      ch->SetChannelId(ch_id[j]);
      int *data_ch = fData+pos; pos += edge_num[j];
      // reading the edges for each channel //
      for(int k = 0; k < edge_num[j]; k++) if(k < EDGE_NUM_LIMIT){
        // the 31st bit of edge[j] indicates if it's a HIGH or VERY HIGH channel
        if(data_ch[k]>>31 & 0x1){ // VERY HIGH resolution mode
          if(33 != ch_nu) ETFMsg::Error("ETFPXIUnpacker", "Unpack: VERY HIGH, but not a TOF card");
          if(data_ch[k]>>30 & 0x1) ch->AddTrailingEdgeV(data_ch[k] & 0x1FFFFF);
          else ch->AddLeadingEdgeV(data_ch[k] & 0x1FFFFF);
        } // end if high
        else{ // HIGH resolution mode
          if(data_ch[k]>>30 & 0x1){
            if(33 == ch_nu) ch->AddTrailingEdgeH(data_ch[k] & 0x7FFFF); // TOF card
            else ch->AddLeadingEdgeH(data_ch[k] & 0x7FFFF); // MWDC card
          } // end if 30th bit
          else{
            if(33 == ch_nu) ch->AddLeadingEdgeH(data_ch[k] & 0x7FFFF); // TOF card
            else ch->AddTrailingEdgeH(data_ch[k] & 0x7FFFF); // MWDC card
          } // end else
        } // end else HIGH
      } // end for and if over edges
    } // end loop over valid channels except the last one which is explained below
    delete [] edge_num;
    // --------------- process the last channel ---------------------- //
    // The last channel of every fired fragment only stores a group header defined
    // by HPTDC@CERN, containing trigger (bunchID) and event index for the fragment
    if(length_last_ch > 0){
      int *data_ch_last = fData+pos; pos += length_last_ch;
      frag->fBunchID = data_ch_last[0] & 0xFFF;
      frag->fEventID = (data_ch_last[0]>>12) & 0xFFF;
    } // end if
    else
      ETFMsg::Error("ETFPXIUnpacker", "Unpack: length of last channel <= 0");
    if(1 != length_last_ch){
      ETFMsg::Info("ETFPXIUnpacker", "Unpack: length of last channel is not 1 word");
      ETFMsg::Info("ETFPXIUnpacker", "Unpack: index %d, frag_num %d, length_last_ch %d",
        sec_h.index, i, length_last_ch);
    } // end if

    if(fFrag_h[i].channel_full) delete [] ch_id;
  } // end loop over fragments
  fPXISection->fSection_len /= 4; // byte => word

  //--------- data integrity check to conclude the section ------------//
  TIter n(fPXISection->GetFragments());
  const unsigned bunchid0 = dynamic_cast<ETFPXIFragment *>(*n.Begin())->GetBunchID();
  const unsigned eventid0 = dynamic_cast<ETFPXIFragment *>(*n.Begin())->GetEventID();
  /// bunchID of the 1st fragment
  ETFPXIFragment *frag;
  while((frag = dynamic_cast<ETFPXIFragment *>(n()))){
    // currently there're only TOF (33) and MWDC (129) cards //
    const int ch_nu = frag->fFragmentHead.channel_num;
    if(33 != ch_nu && 129 != ch_nu){
      ETFMsg::Warn("ETFPXIUnpacker",
        "Unpack: Odd... Channel number of one fragment doesn't equal 33 or 129");
      frag->Print();
    } // end if
    // all fragments should have the same bunchId and eventID //
    if(bunchid0 != frag->GetBunchID() || eventid0 != frag->GetEventID()){
      fBunchIDMisAlignCnt++;
      if(fIsCheckBunchID){
        fPXISection->PrintBunchID();
        fPXISection->PrintEventID();
      } // end if
      break;
    } // end if
  } // end while
  fEventCnt++;

  return true;
} // end member function Unpack

void ETFPXIUnpacker::PrintSummary(){
  cout << "\n>>>>>>>>>>>>>>> PXI DATA Total Event Count <<<<<<<<<<<<<<<<<<<\n" << endl;
  cout << "                            \033[1m" << fEventCnt << "\033[0m\n" << endl;
  cout << ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" << endl;

  if(fIsCheckBunchID)
    cout << "\033[36;1mBunchID misalignment count happened " << fBunchIDMisAlignCnt
         << " times\033[0m\n";

  cout << "\033[45;1m .... .... ... RAW PXI DATA DECODEING HAS BEEN COMPLETED .... .... ... \033[0m\n\n\n" << endl;
} // end member function PrintSummary
